Jx.declare('Jx.code.Compiler', {

	$requires: [

		'Jx.code.Parser',

		'Jx.code.Operation'

	],

	$actions: [

		'parse',

		'compile',

		'purge',

		'registerCompiled'

	],

	$helpers: {

		'Str': 'Jx.lib.String' // used for escaping ECMA strings


	},

	dirtyAttrNameRe: /[^A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-0-9\u00B7\u0300-\u036F\u203F-\u2040]/,

	$onconstruct: function(Class){

		/* synthesize onParse Method */
		function ON_PARSE(production, lexeme){

			var owner = arguments.callee.owner,

				operation = this,

				ret = void(0),

				lexvalue, len, args;

			if (owner.callbackIndex[production]){

				lexvalue = lexeme.value;

				args = [operation, lexeme, lexvalue.length];

				args.push.apply(args, lexvalue);

				ret = owner[ owner.callbackPrefix + production ].apply(owner, args);

				args.splice(0, args.length);

				if (owner.trace){

					console.log('production: ['+ lexeme.production+ '] {', lexvalue.length, '}:', lexeme );

				}

			}

			if (ret === false){

				operation.setError('failed processing production ['+production+'] near: '+ lexeme.getCode());

			}

			return ret;

		}

		ON_PARSE.owner = this;

		this.__callback_Parse = ON_PARSE;

		/* synthesize dirty char to callback */
		function ON_CALLBACK_REPLACE_DIRTY_CHAR(){

			var fn = arguments.callee;

			return fn.original.apply(fn.owner, arguments);

		}

		ON_CALLBACK_REPLACE_DIRTY_CHAR.owner = this;

		ON_CALLBACK_REPLACE_DIRTY_CHAR.original = this.__callback_ReplaceDirtyChar;

		this.__callback_ReplaceDirtyChar = ON_CALLBACK_REPLACE_DIRTY_CHAR;

	},

	$ondestruct: function(Class){

		delete this.onParse.owner;

		delete this.onParse;

	},

	parser: 'Jx.code.Parser',

	callbackPrefix: '__parse_',

	callbackIndex: undefined,

	compiled: undefined,

	compiledNames: undefined,

	compiledTTL: 60 * 1000, /* one minute */

	dirtyCharRe: /^[0-9]|[^a-zA-Z0-9_]+/g,

	bufferCompiled: false,

	trace: false,

	constructor: function(){

		this.callbackIndex = {};

		this.compiled = {};

		this.compiledNames = [];

		this.$super(arguments);

	},

	'__helper_CreateCompiled': function( source, code, parameters ){

		return new Function( parameters, code);

	},

	'__callback_ReplaceDirtyChar': function( matched, position ){

		for(var chrs=[], c = -1, l = matched.length; l--;){

			chrs[++c] = matched.charCodeAt(c).toString(16);

		}

		if (chrs.length > 1){

			chrs[ chrs.length ] = '_';

		}

		return '$'+ chrs.join('')+'$';

	},

	onConstruct: function(){

		this.initializeParser(this.parser);

		this.$super(arguments);

	},

	onDestroy: function(){

		Jx.destruct(

			this.parser,

			this.callbackIndex

		);

		this.purge();

		this.$super(arguments);

	},

	initializeParser: function(parser){

		var J = Jx;

		if (J.isString(parser, true) &&

			J.classExists(parser)

		){

			parser = J.isSingleton(parser) ?

				J.getClass(parser)

				:

				J.instantiate(parser);

		}

		if (J.instanceOf(parser, 'Jx.code.Parser')){

			this.initializeCallbacks(this.parser = parser);

		} else {

			this.parser = null;

			var name = this.$name();

			throw new TypeError('invalid parser ['+ parser+ '] for compiler '+ name);

		}

	},

	initializeCallbacks: function(parser){

		var productions = parser.getProductions(),

			me = this,

			index = me.callbackIndex,

			prefix = me.callbackPrefix,

			J = Jx;

		for(var method, name, l = productions.length; l--;){

			name = productions[l];

			method = prefix + name;

			if (method in me && J.isFunction(me[method])){

				index[ name ] = true;

			}

		}

	},

	allowCompile: function(o, code, recompile){

		var valid = o.valid = Jx.isString(code),

			allCompiled = this.compiled,

			buffered = this.bufferCompiled;

		o.returnValue = void(0);

		o.parsed = false;

		// check workflow
		if (valid){

			// purge before anything else
			if ( buffered ){

				//-- purge
				this.purge( false );

			}

			//-- directly assign
			if ( buffered && recompile !== true && allCompiled.hasOwnProperty(code) ){

				o.execOn = false;

				o.execAfter = false;

				o.returnValue = allCompiled[code];

			//-- parse first!
			} else {

				o.parsed = true;

				var operation = this.parse(code);

				valid = o.valid = operation.isCompleted() && operation.isSuccessful();

				if (valid){

					o.params[1] = operation;

				} else if (this.trace){

					console.log('Operation after Parse Error: ', operation);

				} else {

					operation.destroy();

				}

			}

		}

	},

	onCompile: function(o, operation){

		var params = operation.params,

			source = operation.source,

			codes = operation.codes,

			properties = operation.compiledProperties,

			compiled = o.returnValue = this.__helper_CreateCompiled(source, codes.join("\n"), params.join(','));

		if (Jx.isObject( properties )){

			Jx.assignOwned( compiled, properties );

		}

		o.execAfter = true;

	},

	onAfterCompile: function(o, operation){

		/* register dependencies */
		var fn = o.returnValue;

		fn.dependencies = operation.dependencyNames;

		fn.sourceCode = operation.source;

		var source = operation.source;

		// register
		if (this.bufferCompiled && !this.compiled.hasOwnProperty( source )){

			this.registerCompiled( source, o.returnValue );

		}

		if (this.trace){

			console.log(' intermediate code: \n\n'+ o.returnValue.toString() + '\n\n operation: \n', operation);

		} else {

			operation.destroy();

		}

	},

	allowRegisterCompiled: function(o, source, fn){

		var J = Jx;

		o.returnValue = void(0);

		o.valid = this.bufferCompiled && J.isString(source) && J.isFunction(fn);

	},

	onRegisterCompiled: function(o, source, fn){

		var allCompiled = this.compiled,

			names = this.compiledNames;

		// new
		if (!allCompiled.hasOwnProperty( source )){

			names[ names.length ] = source;

		}

		allCompiled[source] = fn;

		fn.lastUsed = (new Date()).getTime();

		fn.code = source;

		o.returnValue = fn;

	},

	allowParse: function(o, code){

		o.returnValue = void(0);

		var valid = o.valid = Jx.isString(code);

		if (valid){

			o.returnValue = o.params[1] = Jx.instantiate('Jx.code.Operation', [ this, code ]);

			o.execAfter = true;

		}

	},

	onParse: function(o, operation){

		/* register compiler first! */
		operation.registerSymbol( 'compiler', 'session.compiler', true );

		this.parser.parse(operation.source, this.__callback_Parse, operation);

	},

	onAfterParse: function(o, operation){



	},

	onPurge: function(operation, removeAll){

		// purge all
		if (removeAll !== false){

			Jx.clearObject(this.compiled);

			delete this.compiledCounter;

		// purge selected
		} else {

			var names = this.compiledNames,

				fns = this.compiled,

				now = (new Date()).getTime(),

				ttl = this.compiledTTL;

			for(var ttl, name, fn, l = names.length; l--;){

				fn = fns[ name = names[l] ];

				if ((fn.lastUsed + ttl) < now){

					delete fns[ name ];

					names.splice(l, 1);

				}

			}


		}

	},

	createRunSession: function( compiledFn ){

		var compiled = this.compiled,

			registered = {},

			now = (new Date()).getTime();

		for(var found, code, dependencies, l, c, fns = [], fn = compiledFn; fn; fn = fns.pop()){

			dependencies = fn.dependencies;

			fn.lastUsed = now;

			for(c=-1, l = dependencies.length; l--;){

				code = dependencies[++c];

				found = void(0);

				if (!registered.hasOwnProperty(code)){

					registered[ code ] = found = this.compile( code );

					if (found){

						fns[ fns.length ] = found;

					}

				}


			}


		}

		return {

			compiled: registered,

			compiler: this

		};

	},

	destroyRunSession: function( session ){

		var J = Jx;

		J.destruct( session.registered, session);

	},

	dirtyToVarName: function( str ){

		return str.replace( this.dirtyCharRe, this.__callback_ReplaceDirtyChar );

	}

});
